' *** Documentation
' 
' Klenwell VbScript Dataset
' 
' last updated: Oct 2007
' author: klenwell@gmail.com
' 
' Class
' Public Methods
' 	Add key, vData
'   IsKey key
'   Item key
'   Iter  (e.g. For Each key in TestData.Iter())
'   *Items
'   *Remove key
'   *RemoveAll
'   Dump
' 
' *in dev
' 
' NOTES
' 	This class offers a more robust version of the Dictionary object that
'   can store multidimensional data -- compare to PHP's associative arrays
' 
' ____________________________________________________________________________*/


' -- BEGIN CLASS -----------------------------------------------------------
Class VbDataset

' -- PROPERTIES ------------------------------------------------------------

' Utility Properties
Public debug

' Public Properties

' Private Properties
Private m_KEYLIST()
Private m_DATALIST()
Private m_pointer
Private m_listlen     ' tracks length of lists for easier update/maintenance


' -- METHODS ---------------------------------------------------------------
' -- CONSTRUCTOR AND UTILITY METHODS ---------------------------------------		
' Constructor
Private Sub Class_Initialize()
  debug = 0
  m_listlen = -1
  m_pointer = 0
End Sub

' Print Force
Public Sub print_f(str)
  MsgBox(str)
End Sub

' Print when Debug set
Public Sub print_d(str)
  if ( debug ) Then: print_f(str): End If
End Sub



' -- PUBLIC METHODS --------------------------------------------------------
' Public Function
Public Sub Add(key, vData)
  If Not IsKey(key) Then
    m_push_new_dataset key, vData
  Else
    m_push_existing_dataset key, vData
  End If
End Sub


' Public Function
Public Function IsKey(key)
  IsKey = False   ' Return
  
  ' Do we have a keylist array?  If not, then that's a false
  If Not isArray(m_KEYLIST) Then: Exit Function

  ' Loop keylist
  Dim k
  For Each k in m_KEYLIST
    If k = key Then
      IsKey = True: Exit Function
    End If
  Next
  
  ' Else IsKey is still false and gets returned
End Function


Public Function Item(key)
  Dim key_offset: key_offset = m_get_key_offset(key)
  If Not IsEmpty(m_DATALIST(key_offset)) Then
    Item = m_DATALIST(key_offset)
  Else
    Item = False
  End If
End Function


' Iter: For Each key in Dataset.Iter(): ROW = Dataset.Item(key) ... so let's just try to return the keylist array
Public Function Iter()
  Iter = m_KEYLIST
End Function


Public Function fx(arg)
  var = arg
End Function


' dump the object (a la PHP's print_r)
Public Function Dump()
  
  Dim i, dump_string, new_line
  Dim key, TMP_ARRAY
  Dim tabx: tabx = 0
  Dim eol: eol = vbCrlf
  
  Dim rnum, row_type, Row, row_dump, row_line, data_item_dump

  ' loop through listlen (key, data pairs)
  For i = 0 to m_listlen
  
    ' reset cycle data
    key = m_KEYLIST(i)
    data_item_dump = ""
    
    ' get datalist array
    TMP_ARRAY = m_DATALIST(i)
    print_d "key ('"&key&"') -> m_DATALIST("&i&") is '"&TypeName(TMP_ARRAY)&"'"
    
    ' loop through DATALIST for this key to build row_lines
    For rnum = 0 to Ubound(TMP_ARRAY)

      ' get row and data type
      If ( isObject(TMP_ARRAY(rnum)) ) Then
        Set Row = TMP_ARRAY(rnum)
      Else
        Row = TMP_ARRAY(rnum)
      End If
      row_type = Typename(Row)
      
      print_d "key ('"&key&"') -> m_DATALIST("&i&") : row #"&rnum&" is of type '"&row_type&"'"
      
      ' dump by type
      ' dataset object
      If ( row_type = "VbDataset" ) Then
        'row_dump = DumpDataset(Row)
        row_dump = "DumpDataset in dev"
      
      ' array
      ElseIf ( isArray(Row) ) Then
        'row_dump = DumpArray(Row)
        row_dump = "DumpArray in dev"
      
      ' dictionary object
      ElseIf ( row_type = "Dictionary" ) Then
        row_dump = "DumpDictionary in dev"
      
      ' other object
      ElseIf ( isObject(Row) ) Then
        row_dump = "Object"
      
      ' other var
      Else
        row_dump = cstr(Row)
      End If
      
      ' build row dump
      row_line = cstr(rnum)+" ["&row_type&"] : "&row_dump&eol
      data_item_dump = data_item_dump & row_line
    Next
    
    ' Add line to dumpstring
    key_dump = "key #" & cstr(i) & " ('"&key&"') : " & eol
    dump_string = dump_string & key_dump & data_item_dump & eol
  Next
  Dump = dump_string
End Function



' -- PRIVATE METHODS -------------------------------------------------------
' Private Sub
Private Sub m_push_new_dataset(key, vData)
  m_push_new_key key
  m_push_row_to_new_datalist vData
End Sub


' Private Sub
Private Sub m_push_existing_dataset(key, vData)
  m_push_row_to_existing_datalist key, vData
End Sub


Private Sub m_push_new_key(key)
  m_listlen = m_listlen + 1
  ReDim Preserve m_KEYLIST(m_listlen)
  m_KEYLIST(m_listlen) = key
  print_d("adding key ["&cstr(m_listlen)&"] to keylist: '"&key&"'")
End Sub


Private Sub m_push_row_to_new_datalist(vData)
  ReDim Preserve m_DATALIST(m_listlen)
  
  ' All datalist items are stored in arrays
  Dim TMP_ARRAY(0)
  
  ' Set objects, assign others
  If isObject(vData) Then
    Set TMP_ARRAY(0) = vData
  Else
    TMP_ARRAY(0) = vData
  End If
  
  '  Assign temp array to m_DATALIST
  m_DATALIST(m_listlen) = TMP_ARRAY
  
  print_d("adding row of type '"&typename(vData)&"' to m_DATALIST("&cstr(m_listlen)&") for key just added")
End Sub


Private Sub m_push_row_to_existing_datalist(key, vData)
  
  ' first, let's get the offset for the existing key
  Dim key_offset
  key_offset = m_get_key_offset(key)
  
  ' rebuild the array for item at m_DATALIST[key_offset]
  Dim TMP_ARRAY: TMP_ARRAY = m_DATALIST(key_offset)
  Dim new_row: new_row = Ubound(TMP_ARRAY) + 1
  ReDim Preserve TMP_ARRAY(new_row)
  
  If isObject(vData) Then
    Set TMP_ARRAY(new_row) = vData
  Else
    TMP_ARRAY(new_row) = vData
  End If
  
  ' reassign item for array at m_DATALIST[key_offset]
  m_DATALIST(key_offset) = TMP_ARRAY
 
  print_d("adding row of type '"&typename(vData)&"' to m_DATALIST("&cstr(key_offset)&") at key: '"&key&"'")  
End Sub


Private Function m_get_key_offset(key)
  m_get_key_offset = -1   ' return
  Dim i
  For i = 0 to m_listlen
    If ( key = m_KEYLIST(i) ) Then
      m_get_key_offset = i
      Exit Function
    End If
  Next
  Err.Raise Err.Number, Err.Source, "key ["+key+"] does not exist"
End Function


Private Sub m_sub2(str)
  print_d(str)
End Sub



' -- END CLASS -------------------------------------------------------------	
End Class





' -- TEST ------------------------------------------------------------------	
If ( True ) Then
	Dim iT1: iT1 = Timer
	MsgBox("begin testing")
  
  ' Test 0: Construct
  Dim TestClass: Set TestClass = new VbDataset
  TestClass.debug = 1
  TestClass.print_d("VbDataset class successfully created")
  
  ' Test 1: add new datasets
  If ( 1 ) Then
  Dim TEST_ARRAY: TEST_ARRAY = Array(1,2,"String",24)
  Dim TestObject: Set TestObject = CreateObject("Scripting.Dictionary")
  Dim key: key = "key"
  TestClass.Add key, TEST_ARRAY
  TestClass.Add "object", TestObject
  TestClass.Add key, 1
  TestClass.Add key, 2
  TestClass.Add key, "a string"
  TestClass.Add key, TEST_ARRAY
  End If
  
  ' Test 6: Item
  If ( 0 ) Then
    Dim Row, r, row_dump
    Row = TestClass.Item(key)
    For Each r in Row
      row_dump = row_dump & "row of type " & Typename(r) & vbCrLf
    Next
    TestClass.print_f("TestClass.Item(key)" & VbCrlf & row_dump)
  Wscript.Quit
  End If
  
  ' Test 7: Iter
  If ( 0 ) Then
  TestClass.print_f("Iter test")
  For Each key in TestClass.Iter
    row_dump = ""
    For Each r in TestClass.Item(key)
      row_dump = row_dump & "row of type " & Typename(r) & vbCrLf
    Next
    TestClass.print_f("Dumping row for key '"&key&"' in TestClass:" & VbCrlf & row_dump)
  Next
  Wscript.Quit
  End If
  
  ' Test 2: IsKey
  If ( 0 ) Then
    If TestClass.IsKey(key) Then
      TestClass.print_f("IsKey test positive: ok")
    Else
      TestClass.print_f("IsKey test positive: failed")
    End If
    
    If TestClass.IsKey("not_key") Then
      TestClass.print_f("IsKey test negative: failed")
    Else
      TestClass.print_f("IsKey test negative: ok")
    End If
  End If
  
  ' Test 3: add existing object
  If ( 1 ) Then
	TestClass.Add key, "not an array"
  TestClass.Add key, 8
  TestClass.Add "object", 8
  End If
  
  ' Test 4: adding datasets to a dataset object
  if ( 1 ) Then
  TestClass.print_f("let's create a new dataset and add our existing dataset to that")
  Dim Dataset2: Set Dataset2 = new VbDataset
  Dataset2.debug = 1
  Dataset2.Add "TestClass", TestClass
  TestClass.print_f("let's add a dataset to itself!")
  Dataset2.Add "Dataset2", Dataset2
  End If
  
  ' Test 5: dump
  If ( 1 ) Then
    TestClass.print_f("let's take a dump or two")
    TestClass.print_f("TestClass.Dump")
    TestClass.print_f("TestClass is of type " & Typename(TestClass))
    TestClass.print_f(TestClass.Dump)
    
    TestClass.print_f("Dataset2.Dump")
    TestClass.print_f("Dataset2 is of type " & Typename(Dataset2))
    TestClass.print_f(Dataset2.Dump)
  End If 
  
	' Finish
	Dim iT2: iT2 = Timer - iT1
	MsgBox("test complete in " & iT2 & " s")
End If


